using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Cil.OpCodes;

using Nextem.String;
using Nextem.Switch;

namespace Renraku.Compiler {
	public variant CILArg {
		| Stack
		| Local {
			Index : uint
		}
		| Arg {
			Num : int
		}
		| VInt {
			Val : int
		}
		| VUInt {
			Val : uint
		}
		| VShort {
			Val : short
		}
		| VUShort {
			Val : ushort
		}
		
		public override ToString() : string {
			match(this) {
				| Stack => "stack"
				| Local(ind) => "local({0})" <- ind
				| Arg(num) => "arg({0})" <- num
				| VInt(val) => "int({0})" <- val
				| VUInt(val) => "uint({0})" <- val
				| VShort(val) => "short({0})" <- val
				| VUShort(val) => "ushort({0})" <- val
			}
		}
	}
	
	public type CILBody = list [int * Inst [CILArg, int]];
	
	public module CILLoader {
		public Load(fn : string) : Assembly [CILBody] {
			def ParseBody(insts, off, accum : CILBody = []) {
				def bin(op, ovf=false) {
					Inst.Binary(op, ovf, CILArg.Stack(), (CILArg.Stack(), CILArg.Stack()))
				}
				def condBranch(op, inst, un=false) {
					Inst.Branch((op, CILArg.Stack(), CILArg.Stack()), un, (inst.Operand :> Instruction).Offset, inst.Offset+inst.GetSize())
				}
				
				if(off == -1) accum
				else {
					def inst = insts[off];
					if(inst.OpCode == Nop)
						ParseBody(insts, off-1, accum)
					else
						ParseBody(
							insts,
							off-1,
							(
								inst.Offset, 
								switch(inst.OpCode) {
									| Dup => Inst.Push(CILArg.Stack())
									
									| Ldarg_0 => Inst.Push(CILArg.Arg(0))
									
									| Ldc_I4  => Inst.Push(CILArg.VInt(inst.Operand :> int))
									| Ldc_I4_S  => Inst.Push(CILArg.VInt((inst.Operand :> sbyte) :> int))
									| Ldc_I4_M1 => Inst.Push(CILArg.VInt(-1))
									| Ldc_I4_0  => Inst.Push(CILArg.VInt(0))
									| Ldc_I4_1  => Inst.Push(CILArg.VInt(1))
									| Ldc_I4_2  => Inst.Push(CILArg.VInt(2))
									| Ldc_I4_3  => Inst.Push(CILArg.VInt(3))
									| Ldc_I4_4  => Inst.Push(CILArg.VInt(4))
									| Ldc_I4_5  => Inst.Push(CILArg.VInt(5))
									| Ldc_I4_6  => Inst.Push(CILArg.VInt(6))
									| Ldc_I4_7  => Inst.Push(CILArg.VInt(7))
									| Ldc_I4_8  => Inst.Push(CILArg.VInt(8))
									
									| Ldloc_0 => Inst.Push(CILArg.Local(0))
									| Ldloc_1 => Inst.Push(CILArg.Local(0))
									| Ldloc_2 => Inst.Push(CILArg.Local(0))
									| Stloc_0 => Inst.Pop(CILArg.Local(0))
									| Stloc_1 => Inst.Pop(CILArg.Local(1))
									| Stloc_2 => Inst.Pop(CILArg.Local(2))
									| Ldloca_S => Inst.LoadAddr(CILArg.Stack(), CILArg.Local((inst.Operand :> VariableDefinition).Index :> uint))
									
									| Ldelem_I2 => Inst.LoadElem(2, CILArg.Stack(), CILArg.Stack(), CILArg.Stack())
									| Stelem_I2 => Inst.StoreElem(2, CILArg.Stack(), CILArg.Stack(), CILArg.Stack())
									
									| Stind_I2 => Inst.StoreInd(2, CILArg.Stack(), CILArg.Stack())
									
									| Conv_Ovf_U2 => Inst.Conv(true, 2, CILArg.Stack(), CILArg.Stack())
									| Conv_I => Inst.Conv(false, -1, CILArg.Stack(), CILArg.Stack())
									| Conv_U => Inst.Conv(false, 0, CILArg.Stack(), CILArg.Stack())
									
									| Add     => bin(Op.@+)
									| Sub     => bin(Op.@-)
									| Mul     => bin(Op.@*)
									| Div     => bin(Op.@/)
									| Add_Ovf => bin(Op.@+, true)
									| Sub_Ovf => bin(Op.@-, true)
									| Mul_Ovf => bin(Op.@*, true)
									
									| And => bin(Op.@&)
									| Or  => bin(Op.@|)
									| Not => Inst.Unary(Op.@~, CILArg.Stack(), CILArg.Stack())
									
									| Ceq => bin(Op.@==)
									| Cgt => bin(Op.@>)
									| Clt => bin(Op.@<)
									
									| Beq      => condBranch(Op.@==, inst, false)
									| Beq_S    => condBranch(Op.@==, inst, false)
									| Bge      => condBranch(Op.@>=, inst, false)
									| Bge_S    => condBranch(Op.@>=, inst, false)
									| Bge_Un   => condBranch(Op.@>=, inst, true )
									| Bge_Un_S => condBranch(Op.@>=, inst, true )
									| Bgt      => condBranch(Op.@> , inst, false)
									| Bgt_S    => condBranch(Op.@> , inst, false)
									| Bgt_Un   => condBranch(Op.@> , inst, true )
									| Bgt_Un_S => condBranch(Op.@> , inst, true )
									| Ble      => condBranch(Op.@<=, inst, false)
									| Ble_S    => condBranch(Op.@<=, inst, false)
									| Ble_Un   => condBranch(Op.@<=, inst, true )
									| Ble_Un_S => condBranch(Op.@<=, inst, true )
									| Blt      => condBranch(Op.@< , inst, false)
									| Blt_S    => condBranch(Op.@< , inst, false)
									| Blt_Un   => condBranch(Op.@< , inst, true )
									| Blt_Un_S => condBranch(Op.@< , inst, true )
									| Bne_Un   => condBranch(Op.@!=, inst, true )
									| Bne_Un_S => condBranch(Op.@!=, inst, true )
									
									| Br => Inst.Branch((Op.Nil, CILArg.Stack(), null), false, (inst.Operand :> Instruction).Offset, inst.Offset+inst.GetSize())
									| Br_S => Inst.Branch((Op.Nil, CILArg.Stack(), null), false, (inst.Operand :> Instruction).Offset, inst.Offset+inst.GetSize())
									| Brfalse => Inst.Branch((Op.@==, CILArg.Stack(), CILArg.VInt(0)), false, (inst.Operand :> Instruction).Offset, inst.Offset+inst.GetSize())
									| Brfalse_S => Inst.Branch((Op.@==, CILArg.Stack(), CILArg.VInt(0)), false, (inst.Operand :> Instruction).Offset, inst.Offset+inst.GetSize())
									| Brtrue => Inst.Branch((Op.@!=, CILArg.Stack(), CILArg.VInt(0)), false, (inst.Operand :> Instruction).Offset, inst.Offset+inst.GetSize())
									| Brtrue_S => Inst.Branch((Op.@!=, CILArg.Stack(), CILArg.VInt(0)), false, (inst.Operand :> Instruction).Offset, inst.Offset+inst.GetSize())
									
									| Ret => Inst.Return(CILArg.Stack())
									| Break => Inst.Breakpoint()
									
									| _ =>
										print "Unhandled opcode: {0} {1}" <- (inst.OpCode, inst.Operand);
										null
								}
							) :: accum
						)
				}
			}
			
			def asm = AssemblyFactory.GetAssembly(fn);
			
			mutable types = [];
			
			foreach(mod :> ModuleDefinition in asm.Modules)
				foreach(typ :> TypeDefinition in mod.Types) {
					unless(typ.Name == "<Module>") {
						mutable members = [];
						foreach(meth :> MethodDefinition in typ.Methods)
							members ::=
								Assembly.Method.[CILBody](
									meth.Name,
									([], meth.ReturnType.ReturnType.Name),
									ParseBody(meth.Body.Instructions, meth.Body.Instructions.Count-1)
								);
						types ::= Assembly.Class(typ.Name, members)
					}
				}
			
			Assembly.Top(types)
		}
	}
}
